#include "open.h"

#include <cassert> // assert

#include <fcntl.h> // open O_*
#include <errno.h> // errno E*

#include "simple/support/enum.hpp" // support::to_integer

namespace simple::io
{

	unsigned mode::get_flag(mode_value m) noexcept
	{ return 1u << support::to_integer(m); }

	bool mode::is_set(mode_value m) noexcept
	{ return flags & get_flag(m); }

	[[nodiscard]] open_result open(const std::string& path, mode m, permission_flags create_permissions) noexcept
	{
		int posix_flags = 0;

		if(m.is_set(mode::read) && m.is_set(mode::write))
			posix_flags |= O_RDWR;
		else if(m.is_set(mode::read))
			posix_flags |= O_RDONLY;
		else if(m.is_set(mode::write))
			posix_flags |= O_WRONLY;

		if(m.is_set(mode::create))
			posix_flags |= O_CREAT;

		if(m.is_set(mode::exclusive))
			posix_flags |= O_EXCL;

		if(m.is_set(mode::directory))
			posix_flags |= O_DIRECTORY;

		if(m.is_set(mode::truncate))
			posix_flags |= O_TRUNC;

		if(m.is_set(mode::append))
			posix_flags |= O_APPEND;

		if(m.is_set(mode::nonblock))
			posix_flags |= O_NONBLOCK;

		if(m.is_set(mode::nofollow))
			posix_flags |= O_NOFOLLOW;

		if(m.is_set(mode::sync_all))
			posix_flags |= O_SYNC;

		if(m.is_set(mode::sync_data))
			posix_flags |= O_DSYNC;

		if(m.is_set(mode::direct))
#ifdef O_DIRECT
			posix_flags |= O_DIRECT;
#elif
			return operation_not_supported{EOPNOTSUPP};
#endif

		if(m.is_set(mode::no_access_time))
#ifdef O_NOATIME
			posix_flags |= O_NOATIME;
#elif
			return operation_not_supported{EOPNOTSUPP};
#endif

		if(not m.is_set(mode::execute_pass))
			posix_flags |= O_CLOEXEC;

		auto h = unique_handle(static_cast<handle>(::open(path.c_str(), posix_flags, support::to_integer(create_permissions))));
		if(h) return h;
		else switch(errno)
		{
			case EACCES: return access_denied{errno};
			case EEXIST: return already_exists{errno};
			case EISDIR: return path_is_directory{errno};
			case ELOOP:
				if(not m.is_set(mode::nofollow)) return symbolic_link_loop{errno};
				else return path_is_symbolic_link{errno};
			case EMFILE: return process_descriptor_limit_reached{errno};
			case ENFILE: return system_descriptor_limit_reached{errno};
			case ENAMETOOLONG: return path_component_length_exceeded{errno};
			case ENOENT: return no_such_entity{errno};
			case ENOTDIR: return no_such_directory{errno};
			case ENOSPC: return no_space_left_to_create{errno};
			case EOVERFLOW: return file_too_big{errno};
			case EROFS: return filesystem_is_read_only{errno};
			case ETXTBSY: return file_is_executing{errno};
			case EOPNOTSUPP: return operation_not_supported{errno};
			default: return error{errno};
		}
	}

	const char* mode::get_message(mode_value m) noexcept
	{
		switch(m)
		{
			case mode_value::read: return "read";
			case mode_value::write: return "write";
			case mode_value::create: return "create";
			case mode_value::exclusive: return "exclusive";
			case mode_value::directory: return "directory";
			case mode_value::truncate: return "truncate";
			case mode_value::append: return "append";
			case mode_value::nonblock: return "nonblock";
			case mode_value::nofollow: return "nofollow";
			case mode_value::sync_all: return "sync_all";
			case mode_value::sync_data: return "sync_data";
			case mode_value::direct: return "direct";
			case mode_value::no_access_time: return "no_access_time";
			case mode_value::execute_pass: return "execute_pass";
			default: assert(false); return "";
		}
	}


} // namespace simple::io
